import { createContext } from 'react'
import {
  dashboardList,
  dashboardInDirList,
  dashboardInfo,
  dashboardQuery,
  dashboardUpdate,
  datasetListForDashboard
} from './dashboard'
import { datasetInfo } from './dataset'
import { serialize } from '@/utils'
import { TYPE_VALUE_MAP } from '@/utils/schema'
import { omit } from 'loadsh'

const userInfo = {
  authCode: 'sente#tabulate#index',
  bosId: 4000223961714,
  menuId: 10000069600,
  productId: 151,
  type: 1,
  vid: 6000397223714
}

class ApiHub {
  async getDashboards(params) {
    const result = await dashboardList(params)
    const { list = [], pageNo, pageSize, total, totalPage } = result
    return {
      data: dashboardDirList(list),
      page: pageNo,
      pageSize,
      total,
      totalPage
    }
  }
  async getDashboardsForDir(params, list) {
    const result = await dashboardInDirList(params)
    const { directoryId } = params
    const index = list.findIndex((v) => v.dirId === directoryId)
    const dirList = [...list]
    dirList[index].isChildren = true
    dirList[index].isCollapse = true
    dirList.splice(index + 1, 0, ...dashboardDirList(result || [], directoryId))
    return {
      data: dirList
    }
  }
  async getDatasetsForDashboard(params) {
    const { pageInfo = {} } = await datasetListForDashboard(params)
    const { list = [], pageNo, pageSize, total, totalPage } = pageInfo
    const data = list.map(({ datasetId: id, name, ...meta }) => ({
      id,
      name,
      meta
    }))
    return {
      data,
      page: pageNo,
      pageSize,
      totalPage,
      total
    }
  }
  async getDataset(id) {
    const { [id]: info } = await datasetInfo({ ids: [id] })

    const { fieldReqVos, ...meta } = info
    const name = meta.schemaReqVo.name

    const fields = (() => {
      const arr = []
      for (const item of fieldReqVos.values()) {
        if (!item.checked) continue

        const {
          fieldId,
          functionType: fieldType,
          alias: title = '',
          dateFormatType: dateType,
          fieldClassify,
          settings: settingsStr,
          ...meta
        } = item

        const name = title.replace(/\([\u4e00-\u9fa5]+\)$/, '')
        const settings = JSON.parse(settingsStr) || {}
        const field = encodeTypeValue({
          fieldId,
          name,
          title,
          fieldType,
          dateType,
          fieldClassify
        })

        const children =
          settings.dates &&
          settings.dates.map(({ levelGranularity: dateType }) => {
            const subField = {
              ...field,
              dateType,
              title: `${name} (${dateType})`
            }
            subField.id = encodeFieldId(id, subField)
            return subField
          })
        const aggrType = (settings.num ?? settings.str)?.aggregation?.toLowerCase()
        const numFormat = settings.num?.numFormat

        const setFieldProto = (key, value) => value && (field[key] = value)
        setFieldProto('children', children)
        setFieldProto('aggrType', aggrType)
        setFieldProto('numFormat', numFormat)

        field.id = encodeFieldId(id, field)

        arr.push({
          ...field,
          settings,
          meta
        })
      }
      return arr
    })()

    return {
      id,
      name,
      fields,
      meta
    }
  }
  async queryData(query) {
    const queryObject = (() => {
      const { queryFields, aggrMode, ...attrs } = query
      const allFields = []
      const allFilters = []
      Object.entries(queryFields).forEach(([key, fields]) => {
        if (key === 'filter') {
          allFilters.push(fields)
        } else {
          fields.forEach((item) => {
            let aggrType = item.aggrType
            if (item.fieldClassify === 'measure') {
              if (!item.aggrType) {
                aggrType = aggrMode === 'sum' ? 'sum' : 'none'
              }
            }
            allFields.push({ ...item, aggrType, key })
          })
        }
      })
      return {
        fields: allFields,
        filters: allFilters,
        ...attrs
      }
    })()
    const param = (() => {
      const { datasetId, filters: _filters, limit } = queryObject
      const _fields = queryObject.fields.map((v) => decodeTypeValue(v))

      const sortByFields = []
      const fields = _fields.map((item) => {
        const sortType = (() => {
          if (item.sort && item.sort !== 'none') {
            if (item.sort === 'field') {
              let sortByField = item.sortByField || {}
              if (sortByField?.id) {
                sortByField = { ...decodeFieldId(sortByField.id), ...sortByField }
              }
              sortByFields.push(sortByField)
            } else {
              return item.sort
            }
          }
        })()
        return {
          dateFormatType: item.dateType,
          fieldId: item.fieldId,
          opt: item.aggrType,
          alias: item.alias ?? item.title,
          sortType
        }
      })
      sortByFields.forEach((item) => {
        const sortByField = {
          dateFormatType: item.dateType,
          fieldId: item.fieldId,
          opt: item.aggrType,
          alias: item.alias ?? item.title,
          sortType: item.sort,
          hidden: true
        }
        fields.push(sortByField)
      })

      const filters = (() => {
        const arr = []
        for (const item of _filters.values()) {
          const { filter, dateType = 'year' } = item
          if (!filter) continue
          let _filter = { ...filter }
          const { sourceType = 'single', rangeType = 'start', start, end } = _filter
          if (sourceType) {
            const items = (() => {
              if (sourceType === 'single') {
                return [start]
              }
              if (rangeType === 'between') {
                return [start, end]
              }
              if (rangeType === 'start') {
                return [start]
              }
              return [null, end]
            })()
            _filter = {
              combineType: 'and',
              conditions: items
                .map((item, i) => {
                  if (!item) return
                  const operator = i === 1 ? '<=' : sourceType === 'single' ? '=' : '>='
                  const value = (() => {
                    const {
                      type = 'relative',
                      operator = '+',
                      absoluteValue = '',
                      relativeValue = 0
                    } = item
                    if (type === 'relative') {
                      const sign = operator === '-' ? -1 : 1
                      return getRelativeDateValue(dateType, relativeValue * sign)
                    } else {
                      return absoluteValue
                    }
                  })()
                  return {
                    operator,
                    value
                  }
                })
                .filter((v) => v)
            }
          }
          const { combineType: _combineType = 'item', conditions: _conditions = [] } = _filter || {}
          const combineType = ['and', 'or'].includes(_combineType) ? _combineType : 'and'
          const conditions = ['and', 'or'].includes(_combineType)
            ? _conditions
            : _conditions.slice(0, 1)
          const defaultAggrType = item.fieldClassify === 'measure' ? 'sum' : 'none'
          let _item = decodeTypeValue({ aggrType: defaultAggrType, ...item })
          arr.push({
            combineType: 0,
            fieldId: _item.fieldId,
            opt: _item.aggrType,
            dateFormatType: _item.dateType,
            filters: (() => {
              const arr = []
              for (const rule of conditions.values()) {
                let _rule = { ...rule }
                const { operator = '=' } = _rule
                _rule = decodeTypeValue({ ..._rule, operator, combineType })
                arr.push({
                  combineType: _rule.combineType,
                  filterCondition: _rule.operator,
                  value: _rule.value
                })
              }
              return arr
            })()
          })
        }
        return arr
      })()

      return {
        datasetId,
        pageSize: limit,
        pageNum: 1,
        fields,
        filterFields: filters,
        sorts: []
      }
    })()

    if (queryObject.fields.every((field) => field.fieldClassify === 'dimension')) {
      param.groups = param.fields
    }

    const { datas } = await dashboardQuery({
      type: 'ORIGINAL',
      metaId: 0,
      serialType: 1,
      dynamicConfigs: { isDynamicParamsActive: false },
      dashboardId: 322,
      userInfo,
      param
    })
    return {
      data: datas,
      fields: queryObject.fields.map((field, index) => {
        return { ...field, index }
      })
    }
  }
  async setDashboard(dashboard) {
    const { name, system, widgets, layouts, globalValue, delWidgets = {} } = dashboard
    const widgetUpdateReqVos = (() => {
      const _widgets = []
      Object.entries(widgets).forEach(([widgetId, widget]) => {
        let { meta = {}, ...setting } = widget
        setting = omit(setting, ['data', 'updateTime', 'status'])
        const widgetFields = getUsedFieldIds(setting)
        _widgets.push({
          ...meta,
          setting: JSON.stringify(setting),
          widgetId,
          datasetId: setting.query.datasetId,
          widgetFields: JSON.stringify(widgetFields.map((v) => v * 1)),
          beDel: false
        })
      })
      Object.entries(delWidgets).forEach(([widgetId, widget]) => {
        const { meta = {} } = widget
        if (meta.widgetIdSystem) {
          _widgets.push({
            ...meta,
            widgetId,
            beDel: true
          })
        }
      })
      return _widgets
    })()

    const schemaUpdateReqVo = {
      ...system,
      name,
      dashboardId: 322,
      config: JSON.stringify({ layouts, globalValue })
    }

    const params = {
      userInfo,
      schemaUpdateReqVo,
      widgetUpdateReqVos
    }
    const { widgetInfoList } = await dashboardUpdate(params)
    return widgetInfoList
  }
  async getDashboard(params) {
    const { widgetRespVos: _widgets, schemaRespVo: _dashboard } = await dashboardInfo({
      ...params,
      type: 1
    })
    const widgets = (() => {
      const obj = {}
      for (const item of _widgets.values()) {
        const { widgetId, setting: settingStr, ...meta } = item
        const setting = JSON.parse(settingStr)
        obj[widgetId] = {
          meta,
          ...setting
        }
      }
      return obj
    })()
    const dashboard = (() => {
      const { config: configStr, name, ...system } = _dashboard
      const config = JSON.parse(configStr)
      return {
        name,
        config,
        system,
        widgets
      }
    })()
    return dashboard
  }
}

function getUsedFieldIds(widget) {
  return Object.values(widget.query?.queryFields || {})
    .flat()
    .map((field) => field.fieldId)
}

function encodeFieldId(datasetId, { fieldId, dateType, aggrType, compare }) {
  let compareStr
  if (compare) {
    const { id, intervalType, compareType } = compare
    compareStr = [id, intervalType, compareType].map((v) => encodeURIComponent(v)).join('-')
  }
  const params = serialize({ datasetId, dateType, aggrType, compare: compareStr })
  return params ? `${fieldId}?${params}` : fieldId
}

function decodeFieldId(queryFieldId) {
  const entries = queryFieldId.split(/[?&]/g).map((str, i) => {
    if (i === 0) {
      return ['fieldId', str]
    } else {
      return str.split('=')
    }
  })
  return Object.fromEntries(entries)
}

export function encodeTypeValue(object, enumObject = TYPE_VALUE_MAP) {
  object = { ...object }
  for (const key of Object.keys(enumObject).values()) {
    const enums = enumObject[key]
    const enumsEntries = Object.entries(enums)
    if (key in object) {
      const val = object[key]
      for (const [parseValue, value] of enumsEntries.values()) {
        if (object[key] === value) {
          object[key] = parseValue
        }
      }
      if (object[key] === val) {
        delete object[key]
      }
    }
  }
  return object
}

export function decodeTypeValue(object, enumObject = TYPE_VALUE_MAP) {
  object = { ...object }
  for (const key of Object.keys(enumObject).values()) {
    const enums = enumObject[key]
    if (key in object) {
      object[key] = enums[object[key]]
    }
  }
  return object
}

function dashboardDirList(data, directoryId) {
  return data.map((item) => {
    const { dashboardId, dirId, name, dirName, ...meta } = item
    const type = dirId ? 'dir' : 'dashboard'
    return {
      id: `${type}_${dashboardId || dirId}`,
      dashboardId,
      name: name || dirName,
      type,
      dirId: dirId || directoryId,
      isChildren: false,
      isCollapse: false,
      isShow: true,
      meta
    }
  })
}

const api = new ApiHub()
export const ApiContext = createContext(api)
